package com.example.jtt1078_audio.sender;

import android.util.Log;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;


public class ThreadSender extends Thread{
    private static final String TAG = ThreadSender.class.getName();

    private String ip;
    private int port;
    private String deviceId;
    private int logicChannel;

    private Socket socket;
    private OutputStream os;
    private InputStream is;

    private static final int MTU = 1500;
    private BlockingQueue<byte[]> queue;
    private OnConnected onConnected;

    public void setOnConnected(OnConnected onConnected) {
        this.onConnected = onConnected;
    }

    public interface OnConnected{
        void onInputStream(InputStream is);
        void onSendData(byte[] data);
    }

    public ThreadSender(String ip, int port, String deviceId, int logicChannel) {
        int cacheSize = 10 * 1024 * 1024 / MTU;
        this.queue = new LinkedBlockingDeque<>(cacheSize);

        this.ip = ip;
        this.port = port;
        this.deviceId = deviceId;
        this.logicChannel = logicChannel;
    }

    public void putBuffer(byte[] buffer){
        try {
            queue.add(buffer);
        }catch (IllegalStateException e){
            Log.e(TAG, "error, discard this frame");
            e.printStackTrace();
        }
    }

    public byte[] getBuffer(){
        try {
            return queue.poll(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
            return null;
        }
    }


    @Override
    public void run() {
        super.run();
        while (!isInterrupted()){
            try {
                if(socket == null){
                    socket = new Socket(InetAddress.getByName(ip), port);
                    boolean isConnected = socket.isConnected();
                    Log.d(TAG, "isConnected "+isConnected);
                    os = socket.getOutputStream();
                    is = socket.getInputStream();
                    if(isConnected && onConnected != null){
                        onConnected.onInputStream(is);
                    }
                }
                byte[] buffer = getBuffer();
                if(buffer != null){

                    currentTime = System.currentTimeMillis();

                    int i = 0;
                    int length = buffer.length;
                    while (i <= length){
                        byte[] packet = createPacket(
                                buffer, // 帧数据
                                i, // 偏移值
                                (i + 950 > length) ? length - i : 950, // 传输大小
                                !(length > i + 950), // 是否是最后一包
                                payloadType, // 载荷类型 G711A
                                length > i + 950, // 是否有下一包
                                FrameType.Audio, // 当前帧类型，I-frame/P-frame/B-frame/音频/透传
                                currentTime, // 当前时间戳
                                0, // (音频帧没有该数值) 距离上一个 I-frame 关键帧（KeyFrame） 的时间（毫秒）
                                0, // (音频帧没有该数值) 距离上一个 帧（Frame） 的时间（毫秒）
                                false // 是否是视频
                        );
//                        Log.d(TAG, bytes2Hex(packet));
                        if(onConnected != null){
                            onConnected.onSendData(packet);
                        }
                        os.write(packet);
                        os.flush();
                        i+=950;
                    }
                }

            }catch (Exception e){
                e.printStackTrace();
                close(); // 这里会执行吗？
                return;
            }
        }
        close(); // 这里会执行吗？
    }

    public void close(){
        try {
            if(is != null){
                is.close();
            }
            if(os != null){
                os.close();
            }
            if(socket != null){
                socket.close();
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }

    private int payloadType = 6; // G711A
    private long currentTime = 0;
    private int sequenceNumber = 0; // 这里要注意包数一直发可能会超过 int 上限
    private final ByteBuffer buffer = ByteBuffer.allocate(2000);
    private byte[] createPacket(
            byte[] data,
            int offset,
            int size,
            boolean hasMarker,
            int payloadType,
            boolean hasNextPacket,
            FrameType frameType,
            long timeStamp,
            long lastKeyFrameBetweenMs,
            long lastFrameBetweenMs,
            boolean isVideo
    ){
        buffer.clear();
        buffer.put((byte) 0x30);
        buffer.put((byte) 0x31);
        buffer.put((byte) 0x63);
        buffer.put((byte) 0x64);
        buffer.put((byte) Integer.parseInt("10000001", 2));
        String mPlusPT = (hasMarker ? "1" : "0") + padLeft(Integer.toBinaryString(payloadType), 7); // remember to pad Left
        buffer.put((byte) Integer.parseInt(mPlusPT, 2));
        buffer.putShort((short) sequenceNumber++); // 最大值 FFFF 65535，这个后面看情况处理
        StringBuilder sim = new StringBuilder(deviceId);
        int len = sim.length();
        for (int i = len; i < 12; i++) {
            sim.insert(0, "0");
        }
        int i = 0;
        while (i < 12) {
            String sub = sim.substring(i, i + 2);
            buffer.put((byte) Integer.parseInt(sub, 16));
            i += 2;
        }
        buffer.put((byte) logicChannel);
        int packetType = getPacketType(offset, size, hasNextPacket);
        buffer.put(
                (byte) Integer.parseInt(Integer.toHexString(frameType.ordinal()) + Integer.toHexString(packetType), 16)
        );
        StringBuilder hex = new StringBuilder(Long.toHexString(timeStamp));
        len = hex.length();
        for (int j = len; j < 16; j++) {
            hex.insert(0, "0");
        }
        List<String> hexList = splitListByGap(hex.toString(), 2);
        for (String str : hexList) {
            buffer.put((byte) Integer.parseInt(str, 16));
        }
        if(isVideo) buffer.putShort((short) lastKeyFrameBetweenMs); // 距离上一个 I-frame 关键帧（KeyFrame） 的时间（毫秒）
        if(isVideo) buffer.putShort((short) lastFrameBetweenMs); // 距离上一个 帧（Frame） 的时间（毫秒）
        buffer.putShort((short) size);
        buffer.put(data, offset, size);
        return Arrays.copyOfRange(buffer.array(), 0, buffer.position());
    }

    private static int getPacketType(int offset, int size, boolean hasNextPacket) {
        int packetType;
        if (offset == 0)
            if (size < 950)
                packetType = 0; // 原子包，不可被拆分
            else
            if (hasNextPacket)
                packetType = 1; // 分包第一个包
            else
                packetType = 0; // 原子包，不可被拆分
        else
        if (size < 950)
            packetType= 2; // 分包最后一包
        else
        if (hasNextPacket)
            packetType = 3; // 分包中间包
        else
            packetType = 2; // 分包最后一包
        return packetType;
    }

    private List<String> splitListByGap(String hex, int gap) {
        int len = hex.length();
        List<String> list = new ArrayList<>();
        int i = 0;
        while (i < len) {
            list.add(hex.substring(i, i + gap));
            i += gap;
        }
        return list;
    }

    private String bytes2Hex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            int i = ((int) b) & 0xFF;
            String hex = Integer.toHexString(i);
            if (hex.length() < 2) {
                hex = "0"+hex;
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    private String padLeft(String str, int len){
        return padLeft(str, len, '0');
    }

    private String padLeft(String str, int len, char c){
        StringBuilder sb = new StringBuilder(str);
        int length = str.length();
        for(int i = length; i < len; i++){
            sb.insert(0, c);
        }
        return sb.toString();
    }
}
